﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ChromaCode.Models;
using System.Windows.Documents;
using System.Text.RegularExpressions;
using ChromaCode.Extensions;

namespace ChromaCode.Services
{
    /// <summary>
    /// Service for applying a set of regular expression rules to a range of text
    /// </summary>
    internal class RegExRuleService : RuleBaseService<RegExpRule>
    {
        #region Public

        /// <summary>
        /// Initialise the RegExRuleService and start the process of working out which areas of the paragraph to highlight
        /// </summary>
        /// <param name="rules">A list of rules to use</param>
        /// <param name="startParagrph">The paragraph we want to start highlighting blocks at</param>
        public RegExRuleService(List<Rule> rules, Paragraph startParagraph)
        {
            if (rules != null)
            {
                StartParagraph = startParagraph;

                // Get any reg exp rules from the supplied list of rules
                Rules = rules.Where(x => x.TypeOfRule == RuleType.RegExpHighlighter).Select(y => (RegExpRule)y).ToList();

                // If we have rules and a text range then start the highlight process
                if ((Rules != null) && (Rules.Count > 0) && (StartParagraph != null))
                {
                    DoHighlighting();
                }
            }
        }

        #endregion Public

        #region Private

        /// <summary>
        /// Main function that carries out the highlight process
        /// </summary>
        private void DoHighlighting()
        {
            TextRange tr = new TextRange(StartParagraph.ContentStart, StartParagraph.ContentEnd);
            tr.ClearAllProperties();
            int startPos = 0;

            // Keep looping until we find no more rules
            while (FindNextMatch(tr, ref startPos))
            {
            }
        }

        /// <summary>
        /// Find the match for one of the regular expression rules in the supplied text range starting at the supplied
        /// position
        /// </summary>
        /// <param name="range">The text range that contains the text we are searching</param>
        /// <param name="startPos">The position in the text range to start searching from. NB this is a ref variable
        /// so it will be modified to point to the next start point after the rule (if any)</param>
        /// <returns>True if a rule was found and applied</returns>
        private bool FindNextMatch(TextRange range, ref int startPos)
        {
            bool result = false;

            if (startPos < range.Text.Length)
            {
                // The start position is within the text range so get a substring of the ranges text from the
                // start position onwards
                string text = range.Text.Substring(startPos);

                // Find the next matching reg exp rule in the text range
                RegExpRule foundRule = null;
                int foundPos = text.Length;
                int matchLength = 0;
                foreach (RegExpRule regExRule in Rules)
                {
                    Match regExMatch = Regex.Match(text, regExRule.RegExpString);
                    if ((regExMatch.Success) && (regExMatch.Index < foundPos))
                    {
                        // We have a match which is at a position closer than the previous rule's match so record it
                        // before continuing on to check the remaining rules
                        foundPos = regExMatch.Index;
                        foundRule = regExRule;
                        matchLength = regExMatch.Length;
                    }
                }

                if (foundRule != null)
                {
                    // We have found a rule so create a highlight element for it
                    TextPointer startPointer = range.Start.GetInsertationPointAtOffset(startPos + foundPos);
                    TextPointer endPointer = range.Start.GetInsertationPointAtOffset(startPos + foundPos + matchLength);
                    HighlightElement element = new HighlightElement(startPointer, endPointer, foundRule);
                    HighlightElements.Add(element);

                    // Set the next start position
                    startPos = startPos + matchLength;

                    // Set the result to true
                    result = true;
                }
            }

            return result;
        }

        #endregion Private
    }
}
